-- lua wireshark plugin
-- Wireshark Version 2.4.2
-- XXX_TO_XXX_ICD_XXXX/XX/XX (ICD NAME_DATE)
-- 2024-08-22


--[[
模板使用方法：

本模板适用于固定字段的协议解析，暂不支持动态组包的协议

1. 定义接口协议大小端类型、数据包截取位置
2. 在表对象"port_table"中填写端口
3. 在表对象"if_header_description_table"中填写协议头的字段描述
4. 将步骤4中定义Header表时，消息或接口类型字段所在第3步创建的Header表中的序号(起始为1)填入对应的变量中
5. 为每个接口(单方向)协议创建的一张协议字段表对象(例如：if_xx_to_xx_table)
6. 将步骤5中创建的各个字段表对象变量名及对应的协议名称在表对象"IF_Description_Table"中进行填写
7. 将对应的端口号和lua脚本文件填入main.lua脚本文件中
]]--



--[[
注意：

1. 对于步骤5中的预留的字段
	1）如果是某个Byte或者Word中的若干个bit，则可以不定义字段
	2）如果是预留字段是按照整个Byte作为预留的，则必须定义预留字段，格式为：{"reserved", "reserved", nil, nil, nil, "UINT16"}，第二个元素必须为"reserved“
2. 主调用文件和其他脚本文件必须在同一个目录下

]]--




--[[
----------------------------------------------------------step.1----------------------------------------------------------

                                                    step.1 定义接口协议大小端类型
													
--------------------------------------------------------------------------------------------------------------------------
								
]]--
-- 大小端设置, true 为大端， false为小端
local big_endian = true

-- 整个数据包需要去掉的字节数
local data_offset = 20



--[[
----------------------------------------------------------step.2----------------------------------------------------------

                                             在表对象"proto_description_table"填写Proto协议名称及描述
								
--------------------------------------------------------------------------------------------------------------------------								
]]--

--协议描述表:
--元素1 = Proto(name, description)中的#1参数
--元素2 = Proto(name, description)中的#2参数
--参考官方文档Proto的用法

-- 注意：变量名禁止修改
local proto_description_table = {"QDL6_OC_OBC", "QDL6 OC-OBC Communication Protocol"}





--[[
----------------------------------------------------------step.3----------------------------------------------------------

                                            在表对象"if_header_description_table"中填写协议头的字段描述
												 
--------------------------------------------------------------------------------------------------------------------------	
								
]]--

----------------------------------------------------------------------------------创建数据包头Header根表---------------------------------------------------------------------------
-- 创建数据Header表

--数据Header表
--单个元素格式:{abbr, name, base, valuestring, type}
--abbr ： 字段名称，同ProtoField.XXX函数(例如:ProtoField.uint16())abbr，具体格式参考官方Lua API手册中ProtoField,名称中不能有空格
--name ： 字段名称，同ProtoField.XXX函数(例如:ProtoField.uint16())的name参数，具体格式参考官方Lua API手册中ProtoField,名称中不能有空格
--base ： 字段显示类型, 同ProtoField.XXX函数(例如:ProtoField.uint16())base参数，具体格式参考官方Lua API手册中ProtoField
--valuestring ： 同ProtoField.XXX函数(例如:ProtoField.uint16())的valuestring参数，具体格式参考官方Lua API手册中ProtoField
--mask ： 同ProtoField.XXX函数(例如:ProtoField.uint16())mask，具体格式参考官方Lua API手册中ProtoField
--type ： 数据类型，同ProtoField.XXX函数(例如:ProtoField.uint16())的type参数，具体格式参考官方Lua API手册中ProtoField
--flag ： 动态组包标志，true：动态组包，此参数为true时，参数[{子表}]必须填写内容, false：非动态组包，参数[{子表}]可填可不填，不填代表字段节点到此结束，填写参数[{子表}]表示该节点作为父节点，子表中的字段显示在该节点下
--[{子表}] : 可变数据，如果该节点下需要增加子节点，此处添加一张子节点的表，格式同单个元素格式。
-- 注意：变量名禁止修改
local if_header_description_table = {
	{"Interface_Type", "接口信息类型", base.HEX, nil, nil, "UINT16", false},
	{"Sender_Info", "发送方标识信息", base.HEX, nil, nil, "UINT32", false},
	{"Receiver_Info", "接收方标识信息", base.HEX, nil, nil, "UINT32", false},
	{"Data_Ver_Check", "数据版本校验信息", base.HEX, nil, nil, "UINT32", false},
	{"local_seq", "本方消息序列号", base.HEX, nil, nil, "UINT32", false},
	{"device_cycle", "设备运行周期", base.HEX, nil, nil, "UINT16", false},
	{"remote_seq", "对方消息序列号", base.HEX, nil, nil, "UINT32", false},
	{"last_local_seq", "上一条对方消息序列号", base.HEX, nil, nil, "UINT32", false},
	{"Proto_ver", "协议版本", base.HEX, nil, nil, "UINT8", false},
	{"fragment_number", "分包数", base.HEX, nil, nil, "UINT8", false},
	{"fragment_seq", "分包序列号", base.HEX, nil, nil, "UINT8", false},
	{"package_length", "数据包长度", base.HEX, nil, nil, "UINT16", false},
	{"Message_Len", "消息长度", base.HEX, nil, nil, "UINT16", false},
	{"Message_Type", "消息类型", base.HEX, {[0x0101]="OBC_To_OC_Message_基础设备控制信息帧", [0x0102]="OC_To_OBC_Message_基础设备控制信息帧"}, nil, "UINT16", false},
	{"reserved", "reserved", base.HEX, nil, nil, "UINT16", false}
}



--[[
----------------------------------------------------------step.4----------------------------------------------------------

                                      将步骤3中定义Header表时，数据包长度字段以及接口类型字段、分包数量字段、分包序号字段所在的序号(起始为1)填入对应的变量中
												 
--------------------------------------------------------------------------------------------------------------------------	
								
]]--
-- 用于区分消息或接口类型的字段在第3步创建的Header表中的序号，起始为1
-- 注意：变量名禁止修改        
local msg_type_pos = 14				    -- 消息或接口类型字段所在序号
local fragment_num_pos = 10             -- 分包数量字段所在序号，0表示无分包机制




--[[
----------------------------------------------------------step.5----------------------------------------------------------

                                      为每个接口(单方向)协议创建的一张协议字段表对象(例如：if_xx_to_xx_table)
												 
--------------------------------------------------------------------------------------------------------------------------	
								
]]--

----------------------------------------------------------------------------------创建各接口字段(单方向)的根表---------------------------------------------------------------------------

--接口字段表
--单个元素格式:{abbr, name, base, valuestring, mask, type, flag, [{子表}]}
--abbr ： 字段名称，同ProtoField.XXX函数(例如:ProtoField.uint16())abbr，具体格式参考官方Lua API手册中ProtoField,名称中不能有空格
--name ： 字段名称，同ProtoField.XXX函数(例如:ProtoField.uint16())的name参数，具体格式参考官方Lua API手册中ProtoField,名称中不能有空格
--base ： 字段显示类型, 同ProtoField.XXX函数(例如:ProtoField.uint16())base参数，具体格式参考官方Lua API手册中ProtoField
--valuestring ： 同ProtoField.XXX函数(例如:ProtoField.uint16())的valuestring参数，具体格式参考官方Lua API手册中ProtoField
--mask ： 同ProtoField.XXX函数(例如:ProtoField.uint16())mask，具体格式参考官方Lua API手册中ProtoField
--type ： 数据类型，同ProtoField.XXX函数(例如:ProtoField.uint16())的type参数，具体格式参考官方Lua API手册中ProtoField
--flag ： 动态组包标志，true：动态组包，此参数为true时，参数[{子表}]必须填写内容, false：非动态组包，参数[{子表}]可填可不填，不填代表字段节点到此结束，填写参数[{子表}]表示该节点作为父节点，子表中的字段显示在该节点下
--[{子表}] : 可变数据，如果该节点下需要增加子节点，此处添加一张子节点的表，格式同单个元素格式。


--[[
																注意： 如果需要使用mask + valuestring， wireshark只支持到UINT32
																
]]--

-- OBC_To_OC_0101接口表
-- 变量名可以修改
local IF_OBC_To_OC_0101_table = {
	
	{"block_num", "区段数量", base.HEX, nil, nil, "UINT8", true, {
			{"block_id", "ID", base.DEC, nil, nil, "UINT32", false}
		}
	},
	{"switch_num", "道岔数量", base.HEX, nil, nil, "UINT8", true, {
			{"block_id", "ID", base.DEC, nil, nil, "UINT32", false},
			{"ocupy_state", "占用状态", base.DEC, {[0x55] = "占用", [0xaa] = "出清"}, nil, "UINT8", false}
		}
	}
}

-- OC_To_OBC_0102接口表
local IF_OC_To_OBC_0102_table = {
	{"Message_Type", "消息类型", base.HEX, {[0x0101]="OBC_To_OC_Message_基础设备控制信息帧", [0x0102]="OC_To_OBC_Message_基础设备控制信息帧"}, nil, "UINT16", false},
	{"reserved", "reserved", base.HEX, nil, nil, "UINT16", false},
	{"Admin_ID", "管理员ID", base.DEC, nil, nil, "UINT32", false},
	{"ACE_Power_Alarm", "ACE电源报警", base.DEC, {[0x55]="断电报警",[0xaa]="未断电报警"}, nil, "UINT8", false},
	{"block_num", "区段数量", base.HEX, nil, nil, "UINT8", true, {
			{"block_idType", "ID类型", base.HEX, nil, nil, "UINT16", false},
			{"block_id", "ID", base.DEC, nil, nil, "UINT16", false},
			{"ocupy_state", "占用状态", base.DEC, {[0x55]="占用",[0xaa]="出清"}, nil, "UINT8", false},
			{"blockade_state", "封锁状态", base.DEC, {[0x55]="已封锁",[0xaa]="未封锁"}, nil, "UINT8", false}
		}
	},
}



--[[
----------------------------------------------------------step.6----------------------------------------------------------

                                 将步骤5中创建的各个字段表对象变量名及对应的协议名称在表对象"IF_Description_Table"中进行填写
												 
--------------------------------------------------------------------------------------------------------------------------	
								
]]--

--接口描述表: 
--单个元素格式：[接口类型值] = {接口名称, 对应的接口表}
local IF_Description_Table = {
[0x0101] = {"OBC_To_OC_Message_基础设备控制信息帧" , IF_OBC_To_OC_0101_table},
[0x0102] = {"OC_To_OBC_Message_基础设备控制信息帧" , IF_OC_To_OBC_0102_table}
}











--[[------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------


																					脚本已创建完成，此处以下内容禁止修改


-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------]]--

















--[[ 
函数功能： 创建一个子节点

[参数#1]abbr: 作为ProtoField.XXX函数(例如:ProtoField.uint16())的参数，具体格式参考官方Lua API手册中ProtoField
[参数#2]name: 作为ProtoField.XXX函数(例如:ProtoField.uint16())的参数，具体格式参考官方Lua API手册中ProtoField
[参数#3]base: 作为ProtoField.XXX函数(例如:ProtoField.uint16())的参数，具体格式参考官方Lua API手册中ProtoField
[参数#4]valuestring: 作为ProtoField.XXX函数(例如:ProtoField.uint16())的参数，具体格式参考官方Lua API手册中ProtoField
[参数#5]mask: 作为ProtoField.XXX函数(例如:ProtoField.uint16())的参数，具体格式参考官方Lua API手册中ProtoField
[参数#6]type: 作为ProtoField.XXX函数(例如:ProtoField.uint16())的参数，具体格式参考官方Lua API手册中ProtoField
[参数#7]proto: 本文件中的创建的唯一的Proto协议对象

]]--
function crete_proto_field(abbr, name, base, valuestring, mask, type, proto)
	if type == "BOOLEAN" then
		local_field = ProtoField.bool(abbr, name, base, valuestring, mask)
	elseif type == "CHAR" then
		local_field = ProtoField.char(abbr, name, base, valuestring, mask)
	elseif type == "UINT8" then
		local_field = ProtoField.uint8(abbr, name, base, valuestring, mask)
	elseif type == "UINT16" then
		local_field = ProtoField.uint16(abbr, name, base, valuestring, mask)
	elseif type == "UINT24" then
		local_field = ProtoField.uint24(abbr, name, base, valuestring, mask)
	elseif type == "UINT32" then
		local_field = ProtoField.uint32(abbr, name, base, valuestring, mask)
	elseif type == "UINT64" then
		local_field = ProtoField.uint64(abbr, name, base, valuestring, mask)
	elseif type == "INT8" then
		local_field = ProtoField.int8(abbr, name, base, valuestring, mask)
	elseif type == "INT16" then
		local_field = ProtoField.int16(abbr, name, base, valuestring, mask)
	elseif type == "INT24" then
		local_field = ProtoField.int24(abbr, name, base, valuestring, mask)
	elseif type == "INT32" then
		local_field = ProtoField.int32(abbr, name, base, valuestring, mask)
	elseif type == "INT64" then
		local_field = ProtoField.int64(abbr, name, base, valuestring, mask)
	elseif type == "FLOAT" then
		local_field = ProtoField.float(abbr, name, base, valuestring, mask)
	elseif type == "DOUBLE " then
		local_field = ProtoField.double(abbr, name, base, valuestring, mask)
	end
	table.insert(proto.fields, local_field)
	return local_field
end



local field_lengths = {
    ["uint8"] = 1,
    ["uint16"] = 2,
    ["uint24"] = 3,
    ["uint32"] = 4,
    ["uint64"] = 8,
    ["int8"] = 1,
    ["int16"] = 2,
    ["int24"] = 3,
    ["int32"] = 4,
    ["int64"] = 8,
    ["float"] = 4,
    ["double"] = 8
}

function get_field_length(iField)
    -- 提取类型名，例如 "ProtoField.uint8" 提取 "uint8"
    local field_type = tostring(iField):match("ftypes%.(%w+)%s*base")
	--write_log_to_file("log.txt",tostring(iField).. "  " .. field_lengths[string.lower(field_type)])
    return field_lengths[string.lower(field_type)] or "variable"
end



function isReserved(iField)   
	local field_info = tostring(iField):match(":%s*([^:]+)%s+SHJCJY")
	if tostring(field_info) == "reserved" then
		return true
	else
		return false
	end
end

function split(input, delimiter)
    local result = {}
    for match in (input..delimiter):gmatch("(.-)"..delimiter) do
        table.insert(result, match)
    end
    return result
end


function has_mask(iField)
	local function check_zero(line)
		local pairs = split(line, " ")
		if #pairs == 2 then
			--ocal num1 = tonumber(pairs[1], 16)
			local mask = tonumber(pairs[2], 16)
			if mask == 0 then
				return true
			else
				return false
			end
		else
			return nil
		end
	end

	local field_mask = tostring(iField):match("base.%w+ (%w+ %w+)")
	if check_zero(field_mask) then
		return false
	else
		return true
	end
end



function get_uint_value(buf, iTable, pos)
	local res = 0
	local offset = 0
	for index = 1, #iTable do
		local len = get_field_length(iTable[index][1])
		if index == pos then
			if big_endian == true then
				return buf(offset, len):uint()
			else
				return buf(offset, len):le_uint()
			end
		end
		offset = offset + len
	end
	return res
end

-- source_table : 自定义的各接口字段表
-- target_batle : 用于存储各字段信息的表，自动创建
function Define_Field(source_table, target_table, proto)
	for index = 1, #source_table do
		local t_field_description = source_table[index]
		
		local t_field = crete_proto_field(
			proto_description_table[1] .. "." .. t_field_description[1], 
			t_field_description[2], 
			t_field_description[3], 
			t_field_description[4], 
			t_field_description[5], 
			t_field_description[6],
			proto)
		
		local sub_table = {}
		local t_field_table = {t_field, t_field_description[7], sub_table}
		if #t_field_description == 8 then
			Define_Field(t_field_description[8], sub_table, proto)
		end
		table.insert(target_table, t_field_table)
	end
end



local proto_object = Proto(proto_description_table[1], proto_description_table[2])


-- 定义协议头
local Header_Field_Table = {}
Define_Field(if_header_description_table, Header_Field_Table, proto_object)



-- 定义数据字段
for key, value in pairs(IF_Description_Table) do
	local t_table = IF_Description_Table[key]
	local t_if_field_table = {}
	Define_Field(t_table[2], t_if_field_table, proto_object)
	table.insert(IF_Description_Table[key], t_if_field_table)
end










-- 添加字段到节点
function Add_Field_To_Tree(iTree, iFieldTable, buf, iOffset, ending)
	local sub_t = iTree
	local local_offset = iOffset
	local len = 0
	for index = 1, #iFieldTable do
		local tmp_field = iFieldTable[index] -- 获取表元素
		
		-- 非预留字段
		if isReserved(tmp_field[1]) == false then    
			len = get_field_length(tmp_field[1])
			if big_endian == true then
				sub_t = iTree:add(tmp_field[1], buf(local_offset, len))	
			else
				sub_t = iTree:add_le(tmp_field[1], buf(local_offset, len))	
			end
			
		-- 预留字段
		else											   
			local_offset = local_offset + get_field_length(tmp_field[1])
		end
			
		-- 该字段下有挂载的子节点
		if #tmp_field[3] > 0 then  

			 --动态组包
			if tmp_field[2] == true then
				local field_value 
				if big_endian == true then
					field_value = buf(local_offset, len):uint()
				else
					field_value = buf(local_offset, len):le_uint()
				end
				local_offset = local_offset + len
				if field_value > 0 then
					for n = 1, field_value do
						local_offset = Add_Field_To_Tree(sub_t, tmp_field[3], buf, local_offset)
					end
				end
			
			--非动态组包
			else
				Add_Field_To_Tree(sub_t, tmp_field[3], buf, local_offset - len)
			end

		end
		
		-- 没有掩码
		if has_mask(tmp_field[1]) == false then
			local_offset = local_offset + len
		end
		
	end
	return local_offset
end




--[[ 
函数功能： 解析数据头

[参数#1]buf: 去掉Header后需要解析的字段
[参数#2]root: 协议解析的根节点
[参数#3]iTitle: 数据包根节点显示的标题
[参数#4]iFieldTable: 该接口根节点对应的根表

]]--
function Header_Dissector(buf, root, iTable)

	local tree = root:add("Message_Header")

	return Add_Field_To_Tree(tree, iTable, buf, 0, big_endian)

end




--[[ 
函数功能： 解析数据体

[参数#1]buf: 去掉Header后需要解析的字段
[参数#2]root: 协议解析的根节点
[参数#3]iTitle: 数据包根节点显示的标题
[参数#4]iFieldTable: 该接口根节点对应的根表

]]--
function Body_Dissector(buf, root, iTitle, iTable)
	local tree = root:add("Message_Border(".. iTitle..")")
	Add_Field_To_Tree(tree, iTable, buf, 0, big_endian)

end

local fragments = {}

function get_headers_len()
	local len = 0
	for _,item in pairs(Header_Field_Table) do
		local field_len = get_field_length(item[1])
		len = len + field_len
	end
	return len
end


local header_len = get_headers_len()

-- 协议解析
function proto_object.dissector(buf, pkt, root)
	local t_buf = buf(data_offset, buf:len() - data_offset)
	
	local body_data
	
	if fragment_num_pos == 0 then
		body_data = t_buf(header_len, t_buf:len() - header_len)
	else
		local t_fragment_num = get_uint_value(buf(data_offset, buf:len() - data_offset), Header_Field_Table, fragment_num_pos)

		table.insert(fragments, t_buf(header_len, t_buf:len() - header_len))

		if t_fragment_seq < t_fragment_num then
			return
		end

		-- 合并所有数据
		for index, data in ipairs(fragments) do
			if index ==1 then
				body_data = data:range(0, data:len())
			else
				body_data = body_data:range(0, body_data:len()) .. data:range(0, data:len())
			end
		end

		fragments = {}
	end

	
	if big_endian == true then
		root_tree= root:add(proto_object, buf) 
	else
		root_tree= root:add_le(proto_object, buf) 
	end
	
	-- 获取数据字段长度及协议类型
	local msg_type = get_uint_value(t_buf, Header_Field_Table, msg_type_pos)
	
	-- 修改协议名称
	if IF_Description_Table[msg_type] == nil then 
		return
	end
	pkt.cols.protocol = IF_Description_Table[msg_type][1]
	

	-- 解析Header
	Header_Dissector(t_buf, root_tree, Header_Field_Table)

	-- 解析Body
	if body_data:len() == 0 then
		return
	end
	
	Body_Dissector(body_data, root_tree, IF_Description_Table[msg_type][1], IF_Description_Table[msg_type][3])
	
end

return {proto_object, Header_Field_Table, IF_Description_Table}